package javax.realtime;

import java.util.Date;

public class AbsoluteTime extends HighResolutionTime {

	public AbsoluteTime()
	{
		this(0,0);
	}
	
	public AbsoluteTime(AbsoluteTime time)
	{
		if(time == null) throw new IllegalArgumentException();
		milliseconds = time.milliseconds;
		nanoseconds = time.nanoseconds;
		clock = time.clock;
	}
	
	public AbsoluteTime(AbsoluteTime time, Clock clock)
	{
		if(time == null) throw new IllegalArgumentException();
		time.absolute(clock, this);
	}
	
	public AbsoluteTime(Clock clock)
	{
		this(0,0,clock);
	}
	
	public AbsoluteTime(Date date)
	{
		this(date, null);
	}
	
	public AbsoluteTime(Date date, Clock clock)
	{
		if(date == null) throw new IllegalArgumentException();
		if(clock == null) clock = Clock.getRealtimeClock();
		set(date);
		this.clock = clock;
	}
	
	public AbsoluteTime(long millis, int nanos)
	{
		this(millis, nanos, null);
	}
	
	public AbsoluteTime(long millis, int nanos, Clock clock)
	{
		if(clock == null) clock = Clock.getRealtimeClock();
		this.clock = clock;
		if(!_setnormalised(millis, nanos)) throw new IllegalArgumentException();
	}
	
	public AbsoluteTime absolute(Clock clock)
	{
		return absolute(clock, null);
	}
	
	public AbsoluteTime absolute(Clock clock, AbsoluteTime dest)
	{
		if(clock == null) clock = Clock.getRealtimeClock();
		if(dest == null) dest = new AbsoluteTime(0, 0, clock);
		dest.clock = clock;
		
		RelativeTime rtl1 = clock.getEpochOffset();
		RelativeTime rtl2 = dest.clock.getEpochOffset();
		if(!dest._setnormalised(this.milliseconds - rtl2.milliseconds + rtl1.milliseconds, this.nanoseconds - rtl2.nanoseconds + rtl1.nanoseconds)) throw new IllegalArgumentException();
		return dest;
	}
	
	public AbsoluteTime add(long millis, int nanos)
	{
		return add(millis, nanos, null);
	}
	
	public AbsoluteTime add(long millis, int nanos, AbsoluteTime dest)
	{
		if(dest == null) dest = new AbsoluteTime(0, 0, clock);
		else dest.clock = clock;
		
		if(!dest._setnormalised(milliseconds + millis, nanoseconds + nanos)) throw new ArithmeticException();
		return dest;
	}
	
	public AbsoluteTime add(RelativeTime time)
	{
		return add(time, null);
	}
	
	public AbsoluteTime add(RelativeTime time, AbsoluteTime dest)
	{
		if(time == null || clock != time.clock) throw new IllegalArgumentException();
		return add(time.milliseconds, time.nanoseconds, dest);
	}
	
	public Date getDate()
	{
		return new Date(milliseconds + clock.getEpochOffset().milliseconds);
	}
	
	public RelativeTime relative(Clock clock)
	{
		return relative(clock, null);
	}
	
	public RelativeTime relative(Clock clock, RelativeTime dest)
	{
		if(clock == null) clock = Clock.getRealtimeClock();
		if(dest == null) dest = new RelativeTime(0, 0, clock);
		AbsoluteTime absolute = new AbsoluteTime(this.getMilliseconds(), this.getNanoseconds());
		clock.getTime().subtract(absolute, dest);
		return dest;
	}
	
	public void set(Date date)
	{
		if(date == null) throw new IllegalArgumentException();
		milliseconds = date.getTime();
		nanoseconds = 0;
		clock = Clock.getRealtimeClock();
	}
	
	public RelativeTime subtract(AbsoluteTime time)
	{
		return subtract(time, null);
	}
	
	public RelativeTime subtract(AbsoluteTime time, RelativeTime dest)
	{
		if(time == null) throw new IllegalArgumentException();
		if(time.clock != clock) throw new IllegalArgumentException();
		if(dest == null) dest = new RelativeTime(0,0,clock);
		return _subtract(time.milliseconds, time.nanoseconds, dest);
	}
	
	public AbsoluteTime subtract(RelativeTime time)
	{
		return subtract(time, null);
	}
	
	public AbsoluteTime subtract(RelativeTime time, AbsoluteTime dest)
	{
		if (time == null) throw new IllegalArgumentException();
		if(time.clock != clock) throw new IllegalArgumentException();
        if (dest == null) dest = new AbsoluteTime(0, 0, clock);
        if (!dest._setnormalised(addSafe(this.milliseconds, -time.milliseconds), this.nanoseconds - time.nanoseconds))
            throw new ArithmeticException();
        dest.clock = this.clock;
        return dest;
	}
	
	public String toString()
	{
		return "(" + milliseconds + " ms, " + nanoseconds + " ns)";
	}
	
	// Methods from OVM v1.4
	RelativeTime _subtract(long millis, int nanos, RelativeTime dest) {
        if (dest == null)
            dest = new RelativeTime(0, 0, this.clock);
        if (!dest._setnormalised(addSafe(this.milliseconds, -millis),
                                this.nanoseconds - nanos) )
            throw new ArithmeticException("non-normalizable result");
        // note: don't change dest until we know no exceptions will occur
        dest.clock = this.clock;
        return dest;
    }
}
